/*实现异步工作器*/


#ifndef __M_LOOPER_H__
#define __M_LOOPER_H__

#include "buffer.hpp"
#include <mutex>
#include <condition_variable>
#include <thread>
#include <functional>
#include <memory>
#include <atomic>

using namespace std;

namespace log{

    //回调函数的类型
    using Func = function<void(Buffer &)>;

    //阻塞（固定大小）和非阻塞（扩容）的添加方式
    enum AsyncType{
        ASYNC_SAFE,     //阻塞
        ASYNC_UNSAFE    //非阻塞
    };

    class AsyncLooper{
    public:
        using ptr = shared_ptr<AsyncLooper>;

        AsyncLooper(const Func &callBack, AsyncType type = ASYNC_SAFE)
            : _stop(false), _thread(thread(&AsyncLooper::threadEntry, this)), _callBack(callBack), _type(type){

        }

        ~AsyncLooper(){
            stop();
        }

        void stop(){
            _stop = true;

            //唤醒所有处理缓冲区
            _ccond.notify_all();

            //等待工作线程退出之后才能退出
            _thread.join();
        }

        void push(const char* data, size_t len){
            //两种缓冲区管理方式：
            //  1.无限扩容（不安全）,当内存耗尽的时候会造成数据丢失
            //  2.固定大小 -- 生产缓冲区满了就阻塞
            unique_lock<mutex> lock(_mtx);

            //固定大小 -- 若生产缓冲区剩余大小 >= 数据长度，则可以添加数据（返回true）
            if(_type == ASYNC_SAFE){
                //这里一定要用lambda表达式，不能用bool值直接写_pbuf.writeableSize() >= len
                //  因为在cond.wait()调用的时候，bool值会作为固定的参数传入，在cond.wait()运行的过程中这个bool的值不会改变，一开始是true就是true，一开始是false就是false
                //  如果传入一个lambda表达式或函数返回值，当lambda表达式中的变量改变的时候会重新计算返回值传入cond.wait()，对cond.wait()中的参数进行改变
                //  这样就实现了一开始传入false进行阻塞，后面更新后传入true进行唤醒
                _pcond.wait(lock, [&]()
                        { return _pbuf.writeableSize() >= len; });
            }
            
            //能够走到这里代表_pcond未被阻塞，可以添加数据
            _pbuf.push(data, len);
            //唤醒一个处理缓冲区，对缓冲区中的数据进行处理
            //  处理缓冲区可能在完成自己的工作后检测到生产缓冲区为空，进入休眠，那么就需要再生产缓冲区满的时候唤醒处理缓冲区
            _ccond.notify_one();
        }

    private:
        //线程入口函数 -- 工作线程，对处理缓冲区中的数据进行处理，处理完后交换缓冲区
        void threadEntry(){
            //当停止标志为true，停止运行
            //  这样会造成一个问题：当停止运行、main结束之后，可能生产缓冲区还有数据尚未写入，main结束调用析构函数清理缓冲区，会造成数据丢失
            //  所以需要加上对生产缓冲区是否为空的判断，当生产缓冲区为空且停止标志为true，停止运行
            //但是写在while的循环条件中需要写成 当生产缓冲区不为空或停止标志不为true，进入循环，这样可读性差，所以这里写成死循环，在内部通过break来退出
            while(1){
                // 1.一开始处理缓冲区为空，判断生产缓冲区是否为空，为空则阻塞，不为空则交换
                //  在交换完之后就要进行解锁，让生产者能写入数据，提高效率
                {
                    unique_lock<mutex> lock(_mtx);

                    //生产缓冲区为空且停止标志为true
                    if(_stop && _pbuf.empty()){
                        break;
                    }

                    //当生产缓冲区为空或停止标志位为false，阻塞
                    //  要判断_stop是为了让其及时处理完数据并退出，而不是一直阻塞等待产生死锁
                    _ccond.wait(lock, [&]()
                                { return !_pbuf.empty() || _stop; });
                    _cbuf.swapBuffer(_pbuf);
                }
               
                // 2、唤醒生产缓冲区（生产缓冲区可能因为数据满了而阻塞）
                _pcond.notify_all();

                // 3、完成交换/处理缓冲区被唤醒 后，进行数据处理
                _callBack(_cbuf);

                // 4、初始化处理缓冲区
                _cbuf.reset();
            }

        } 

    private:
        Buffer _pbuf;       //生产缓冲区
        Buffer _cbuf;       //处理缓冲区
        mutex _mtx;
        condition_variable _pcond;      //生产缓冲区条件变量
        condition_variable _ccond;      //处理缓冲区条件变量
        atomic<bool> _stop;     //异步工作器是否停止的标识符，原子类型，保证线程安全
        thread _thread;     //异步工作器的工作线程
        Func _callBack;     //具体对缓冲区数据进行处理的回调函数，由使用者传入
        AsyncType _type;
    };
}

#endif